package rw.main;


import rw.rwbehaviour.RwBehaviour;
import rw.rwbehaviour.Vector2;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.LinkedList;

/**
 * 用于显示的画板
 *
 * @author RWdaze
 */
public class PaintPanel extends JPanel {
    /**
     * 刷新整个屏幕。需要注意的是，这和主线程并不是一个线程
     * 所以使用了阻塞队列，使用阻塞的方式强行让该线程和主线程同步
     * 如果不这样做，在游戏对象多的时候，很容易抛出异常。
     * <p>
     * 但是如果非常追求游戏运行效率，那其实可以不用阻塞。
     * 因为如果一帧没有刷新出来，不影响游戏的正常运行
     * 对于玩家来说也没有太大感官上的影响。
     * <p>
     * 只是可能在输出框里面看到疯狂的红色错误而已
     *
     * @param g 画笔
     */
    @Override
    public void paint(Graphics g) {
        // 父类先进行画板的基础操作
        super.paint(g);
        // 新建一张大图，这张大图就是最后会被画到画板上最终的图片
        BufferedImage image = new BufferedImage(Main.WIDTH, Main.HEIGHT, BufferedImage.TYPE_4BYTE_ABGR_PRE);
        // 以下三行来自网上，我也不懂，推测和生成新图片有关系
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        GraphicsConfiguration gc = gd.getDefaultConfiguration();

        // 获取主函数中的所有游戏对象
        LinkedList<RwBehaviour> gameObjects = Main.getGameObjects();
        RwBehaviour[] array = new RwBehaviour[gameObjects.size()];
        array = gameObjects.toArray(array);
        // 利用游戏对象中的显示优先级进行排序，
        // 显示优先级小的放在前面画，显示优先级大的放在后面画，则大的就会盖住小的
        Arrays.sort(array);
        // 遍历所有游戏对象
        for (RwBehaviour gameObject : array) {
            // 如果游戏对象激活，并且可见，并且图像不为空时。
            if (gameObject.active && gameObject.visible && gameObject.image != null) {
                // 根据游戏属性获得相应的图片对象
                BufferedImage drawImage = create(gameObject.image, gameObject.alpha, gameObject.angle / 180 * Math.PI,
                        gameObject.scale, gc);

                // 原本图片的坐标中心是左上角，显示的坐标也是根据图片左上角定位的
                // 这里根据计算，可以做出让图片中心放在目标坐标上的效果
                Vector2 position = new Vector2();
                position.x = gameObject.position.x - (drawImage.getWidth() / 2f);
                position.y = gameObject.position.y - (drawImage.getHeight() / 2f);

                // 把游戏对象画在最大的那张图上面
                g.drawImage(drawImage, Math.round(position.x), Math.round(position.y), drawImage.getWidth(),
                        drawImage.getHeight(), this);
            }
        }

        // 计算完所有的游戏对象，画出最大的图
        g.drawImage(image, 0, 0, this);

        // 执行完毕，解除主线程的阻塞
        try {
            Main.bq.put(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据游戏对象的属性来计算出相应的图片
     *
     * @param image 游戏对象的图片
     * @param alpha 游戏对象的透明度
     * @param angle 游戏对象的旋转角度
     * @param scale 游戏对象的横纵向拉伸度
     * @param gc    画笔（大概）
     * @return 计算完毕之后的图片
     */
    public BufferedImage create(BufferedImage image, float alpha, double angle, Vector2 scale,
                                GraphicsConfiguration gc) {
        // 计算出拉伸之后图片本应该拥有的宽和高
        double sw = image.getWidth() * scale.x;
        double sh = image.getHeight() * scale.y;

        // 计算出图片旋转之后，占大图的宽和高
        double w = Math.floor(Math.abs(sh * Math.sin(angle)) + Math.abs(sw * Math.cos(angle))) + 1;
        double h = Math.floor(Math.abs(sw * Math.sin(angle)) + Math.abs(sh * Math.cos(angle))) + 1;

        // 新建图片 从网上抄的，推测和新建图片有关。
        int transparency = image.getColorModel().getTransparency();
        BufferedImage result = gc.createCompatibleImage((int) Math.floor(w), (int) Math.floor(h), transparency);
        Graphics2D g = result.createGraphics();

        // 控制透明度，不会超过范围
        if (alpha > 1) {
            alpha = 1;
        }
        if (alpha < 0) {
            alpha = 0;
        }
        // 调整图片透明度
        AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
        g.setComposite(ac);
        // 图片先平移，移到大图片的中心
        g.translate((w - sw) / 2, (h - sh) / 2);
        // 然后图片旋转，旋转的相应的位置
        g.rotate(angle, sw / 2, sh / 2);
        // 图片拉伸，拉伸到相应位置
        g.scale(scale.x, scale.y);
        // 大功告成，完成图片，并且释放资源
        g.drawRenderedImage(image, null);
        g.dispose();

        return result;
    }
}
